﻿using Microsoft.EntityFrameworkCore;
using Cyss.Core;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Concurrent;
using Cyss.Core.Cache;
using Microsoft.AspNetCore.Http;

namespace Cyss.Core.Repository
{

    /// <summary>
    /// 缓存业务逻辑层事务，不锁表,分布式事务中只需要执行错误时回滚,不想锁表的操作
    /// </summary>
    public class LogicalTransaction : IDisposable
    {
        /// <summary>
        /// 是否开启逻辑事务
        /// </summary>
        private bool IsLogicalTransaction = false;

        private ConcurrentQueue<DataBaseWork> dataWorks = null;

        private string _TransactionId { set; get; }
        private bool _IsAsyncMessageTransaction { set; get; }

        private IStaticCacheManager _staticCacheManager { get { return IOCEngine.Resolve<IStaticCacheManager>(); } }

        /// <summary>
        /// 缓存Id
        /// </summary>
        private string _CacheId { set; get; }

        /// <summary>
        /// 消息队列Queue
        /// </summary>
        private string QueueName { set; get; }

        /// <summary>
        /// 缓存业务逻辑层事务，不锁表,分布式事务中只需要执行错误时回滚,不想锁表的操作
        /// </summary>
        public LogicalTransaction()
        {
            dataWorks = new ConcurrentQueue<DataBaseWork>();
            Open();
        }

        /// <summary>
        /// 缓存业务逻辑层事务，不锁表,分布式事务中只需要执行错误时回滚,不想锁表的操作
        /// </summary>
        /// <param name="IsAsyncMessageTransaction">是否开启异步消息事务</param>
        /// <param name="TransactionId">分布式事务ID</param>
        public LogicalTransaction(bool IsAsyncMessageTransaction, string TransactionId, string QueueName)
        {
            Open();
            dataWorks = new ConcurrentQueue<DataBaseWork>();
            this.QueueName = QueueName;
            IsLogicalTransaction = true;
            _IsAsyncMessageTransaction = IsAsyncMessageTransaction;
            _TransactionId = TransactionId;
            _CacheId = TransactionId + "-" + Guid.NewGuid().ToString().Replace("-", "");
        }

        private void Open()
        {
            IsLogicalTransaction = true;
            var httpContextAccessor = IOCEngine.Resolve<IHttpContextAccessor>();
            if (httpContextAccessor == null)
            {
                throw new Exception("请注册IHttpContextAccessor");
            }
            if (httpContextAccessor.HttpContext == null)
            {
                throw new Exception("httpContextAccessor.HttpContext 不能为null");
            }
            httpContextAccessor.HttpContext.Items.Add("IsLogicalTransaction", true);
            httpContextAccessor.HttpContext.Items.Add("LogicalTransaction", this);

        }
        private void Close()
        {
            IsLogicalTransaction = false;
            var httpContextAccessor = IOCEngine.Resolve<IHttpContextAccessor>();
            if (httpContextAccessor == null)
            {
                throw new Exception("请注册IHttpContextAccessor");
            }
            if (httpContextAccessor.HttpContext == null)
            {
                throw new Exception("httpContextAccessor.HttpContext 不能为null");
            }
            httpContextAccessor.HttpContext.Items["IsLogicalTransaction"] = false;
            httpContextAccessor.HttpContext.Items["LogicalTransaction"] = null;
        }

        internal static bool IsOpenLogicalTransaction
        {
            get
            {
                var httpContextAccessor = IOCEngine.Resolve<IHttpContextAccessor>();
                if (httpContextAccessor == null || httpContextAccessor.HttpContext == null)
                {
                    return false;
                }
                if (!httpContextAccessor.HttpContext.Items.ContainsKey("IsLogicalTransaction"))
                {
                    return false;
                }
                var value = httpContextAccessor.HttpContext.Items["IsLogicalTransaction"];
                return (bool)value;
            }
        }

        internal static LogicalTransaction GetLogicalTransaction
        {
            get
            {
                var httpContextAccessor = IOCEngine.Resolve<IHttpContextAccessor>();
                if (httpContextAccessor == null || httpContextAccessor.HttpContext == null)
                {
                    return null;
                }
                if (!httpContextAccessor.HttpContext.Items.ContainsKey("IsLogicalTransaction"))
                {
                    return null;
                }
                var value = httpContextAccessor.HttpContext.Items["LogicalTransaction"];
                return (LogicalTransaction)value;
            }
        }
        internal void Insert<TEntity>(IEnumerable<TEntity> entitys, IDbContext dbContext) where TEntity : BaseEntity
        {
            if (IsLogicalTransaction)
            {
                foreach (var entity in entitys)
                {
                    Insert(entity, dbContext);
                }
            }
        }

        internal void Insert<TEntity>(TEntity entity, IDbContext dbContext) where TEntity : BaseEntity
        {
            if (IsLogicalTransaction)
            {
                dataWorks.Enqueue(new DataBaseWork() { Id = entity.GetIdValue(), Table = typeof(TEntity).Name, dbContext = dbContext, workType = WorkType.Insert });
            }
        }

        internal List<DataBaseWork> Change<TEntity>(IDbContext dbContext, IEnumerable<TEntity> entitys) where TEntity : BaseEntity
        {

            if (IsLogicalTransaction)
            {
                List<DataBaseWork> dataWorks = new List<DataBaseWork>();

                foreach (var entity in entitys)
                {
                    var work = Change(dbContext, entity);
                    if (work != null)
                    {
                        dataWorks.Add(work);
                    }
                }
                return dataWorks;
            }
            return null;
        }
        internal DataBaseWork Change<TEntity>(IDbContext dbContext, TEntity entity) where TEntity : BaseEntity
        {

            if (IsLogicalTransaction)
            {
                List<UpdateFiled> updateFileds = null;
                DataBaseWork dataWork = null;

                updateFileds = new List<UpdateFiled>();
                var entityEntry = dbContext.Entrys(entity);

                foreach (var propertyName in entityEntry.OriginalValues.Properties)
                {
                    object OriginalValue = entityEntry.OriginalValues[propertyName];
                    object CurrentValue = entityEntry.CurrentValues[propertyName];
                    if (OriginalValue.ToString() != CurrentValue.ToString())
                    {
                        updateFileds.Add(new UpdateFiled() { Filed = propertyName.Name, Value = OriginalValue });
                    }
                }

                if (updateFileds.Count > 0)
                {
                    //dataWork = new DataWork() { Id = entity.Id, Table = typeof(TEntity).Name, dbContext = dbContext, updateFileds = updateFileds, workType = WorkType.Update };
                }
                return dataWork;
            }
            return null;
        }
        internal List<DataBaseWork> DeleteOriginal<TEntity>(IDbContext dbContext, IEnumerable<TEntity> entitys) where TEntity : BaseEntity
        {
            List<DataBaseWork> dataWorks = new List<DataBaseWork>();

            if (IsLogicalTransaction)
            {
                foreach (var entity in entitys)
                {
                    var work = DeleteOriginal(dbContext, entity);
                    if (work != null)
                    {
                        dataWorks.Add(work);
                    }
                }
            }
            return dataWorks;
        }
        internal DataBaseWork DeleteOriginal<TEntity>(IDbContext dbContext, TEntity entity) where TEntity : BaseEntity
        {
            List<UpdateFiled> updateFileds = null;
            DataBaseWork dataWork = null;
            if (IsLogicalTransaction)
            {
                updateFileds = new List<UpdateFiled>();
                var entityEntry = dbContext.Entrys(entity);

                foreach (var propertyName in entityEntry.OriginalValues.Properties)
                {
                    object OriginalValue = entityEntry.OriginalValues[propertyName];

                    if (OriginalValue.GetType() == typeof(DateTime))
                    {
                        OriginalValue = ((DateTime)OriginalValue).ToString("yyyy-MM-dd HH:mm:ss:fff");
                    }
                    updateFileds.Add(new UpdateFiled() { Filed = propertyName.Name, Value = OriginalValue });
                }
                if (updateFileds != null && updateFileds.Count > 0)
                {
                    dataWork = new DataBaseWork() { Id = entity.GetIdValue(), Table = typeof(TEntity).Name, dbContext = dbContext, updateFileds = updateFileds, workType = WorkType.Delete };
                }
            }
            return dataWork;
        }

        internal void Update(List<DataBaseWork> dataWorks)
        {
            if (IsLogicalTransaction && dataWorks != null)
            {
                foreach (var dataWork in dataWorks)
                {
                    Update(dataWork);
                }
            }
        }

        internal void Update(DataBaseWork dataWork)
        {
            if (IsLogicalTransaction)
            {
                if (dataWork != null)
                {
                    dataWorks.Enqueue(dataWork);
                }
            }

        }


        internal void Delete(DataBaseWork dataWork)
        {
            if (IsLogicalTransaction)
            {
                if (dataWork != null)
                {
                    dataWorks.Enqueue(dataWork);
                }
            }

        }

        internal void Delete(List<DataBaseWork> dataWorks)
        {
            if (IsLogicalTransaction)
            {
                foreach (var dataWork in dataWorks)
                {
                    Delete(dataWork);
                }
            }
        }

        /// <summary>
        /// 回滚
        /// </summary>
        public void Rollback()
        {
            while (dataWorks.Count > 0)
            {
                DataBaseWork work = null;
                dataWorks.TryDequeue(out work);
                if (work.workType == WorkType.Insert)
                {

                    string rawSqlString = $" delete from {work.Table} where Id={work.Id} ";
                    work.dbContext.ExecuteSqlCommand(rawSqlString);
                }

                if (work.workType == WorkType.Update)
                {
                    StringBuilder stringBuilder = new StringBuilder();

                    foreach (var item in work.updateFileds)
                    {
                        if (stringBuilder.ToString() != string.Empty)
                        {
                            stringBuilder.Append(" , ");
                        }
                        stringBuilder.AppendFormat(" {0}='{1}' ", item.Filed, item.Value);
                    }

                    string rawSqlString = $" update [{work.Table}] set {stringBuilder.ToString()} where Id={work.Id} ";

                    work.dbContext.ExecuteSqlCommand(rawSqlString);

                }

                if (work.workType == WorkType.Delete)
                {
                    StringBuilder stringBuilderValue = new StringBuilder();
                    StringBuilder stringBuilderFiled = new StringBuilder();

                    foreach (var item in work.updateFileds)
                    {
                        if (stringBuilderFiled.ToString() != string.Empty)
                        {
                            stringBuilderFiled.Append(" , ");
                        }
                        stringBuilderFiled.AppendFormat(" {0} ", item.Filed);

                        if (stringBuilderValue.ToString() != string.Empty)
                        {
                            stringBuilderValue.Append(" , ");
                        }
                        stringBuilderValue.AppendFormat(" '{0}' ", item.Value);
                    }

                    string rawSqlString = $" set IDENTITY_INSERT [{work.Table}] ON    insert into [{work.Table}]({stringBuilderFiled.ToString()}) values({stringBuilderValue.ToString()})  ";

                    work.dbContext.ExecuteSqlCommand(rawSqlString);
                }
            }
            if (_IsAsyncMessageTransaction)
            {
                var staticCacheManager = IOCEngine.Resolve<IStaticCacheManager>();
                var transactionCacheCollections = staticCacheManager.Get<TransactionCacheCollections>(_CacheId, () => { return null; });
                var transactionCache = transactionCacheCollections.GetTransactionCache(QueueName);
                transactionCache.RollbackStatus = TransactionRollbackStatus.Success;
                staticCacheManager.Set(_TransactionId, transactionCacheCollections, 30);
            }

        }

        /// <summary>
        /// 
        /// </summary>
        public void Success()
        {
            if (_IsAsyncMessageTransaction)
            {
                var staticCacheManager = IOCEngine.Resolve<IStaticCacheManager>();
                var transactionCacheCollections = staticCacheManager.Get<TransactionCacheCollections>(_CacheId, () => { return null; });
                var transactionCache = transactionCacheCollections.GetTransactionCache(QueueName);
                transactionCache.Status = TransactionCacheStatus.Success;
                staticCacheManager.Set(_TransactionId, transactionCacheCollections, 30);
            }
        }

        public void Fail()
        {
            if (_IsAsyncMessageTransaction)
            {
                var staticCacheManager = IOCEngine.Resolve<IStaticCacheManager>();
                var transactionCacheCollections = staticCacheManager.Get<TransactionCacheCollections>(_CacheId, () => { return null; });
                var transactionCache = transactionCacheCollections.GetTransactionCache(QueueName);
                transactionCache.Status = TransactionCacheStatus.Fail;
                staticCacheManager.Set(_TransactionId, transactionCacheCollections, 30);
            }
        }

        public void Dispose()
        {
            Close();
            IsLogicalTransaction = false;
            dataWorks = null;
        }
    }

    internal class DataBaseWork
    {
        public object Id { set; get; }

        public string Table { set; get; }

        public IDbContext dbContext { set; get; }

        public List<UpdateFiled> updateFileds { set; get; }

        public WorkType workType { set; get; }
    }

    internal class UpdateFiled
    {
        public string Filed { set; get; }

        public object Value { set; get; }
    }

    internal enum WorkType
    {
        Insert = 1,
        Update = 2,
        Delete = 3,
    }
}
